package cn.uestc.labhms.modules.hms.listener;

import cn.hutool.core.util.StrUtil;
import cn.uestc.labhms.modules.hms.dao.HmsChiEngDao;
import cn.uestc.labhms.modules.hms.dao.XlsxDao;
import cn.uestc.labhms.modules.hms.exception.HMSException;
import cn.uestc.labhms.modules.hms.param.ConstParam;
import cn.uestc.labhms.modules.hms.util.SpringUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 直接用map接收数据
 *
 * @author Mu.tz
 */
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
    private static final Logger LOGGER = LoggerFactory.getLogger(NoModelDataListener.class);
    /**
     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 3000;
    private List<Map<String, String>> allData;
    private Map<Integer, String> headMap;
    private final HmsChiEngDao hmsChiEngDao;
    private final XlsxDao xlsxDao;
    private final String daoRefPath = ConstParam.DAO_REF_PATH;
    private final String entityRefPath = ConstParam.ENTITY_REFE_PATH;
    /**
     * 表单名字，对应的数据表的中文名
     */
    private final String sheetName;

    public NoModelDataListener(HmsChiEngDao hmsChiEngDao, XlsxDao xlsxDao, String sheetName) {
        this.xlsxDao = xlsxDao;
        this.hmsChiEngDao = hmsChiEngDao;
        this.sheetName = sheetName;
        allData = new LinkedList<>();
    }


    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        Map<String, String> row = this.parseData(this.headMap, data, sheetName);
        allData.add(row);

    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) SpringUtil.getBean("transactionManager");
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            for (Map<String, String> row : allData) {
                saveOrUpdateData(row, sheetName);
            }
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
        LOGGER.info("所有数据解析完成！");
    }

    /**
     * 通过表头和单元格数据做数据拼接
     * @param headMap
     * @param row
     * @return key为表头，val为具体的值的map数据
     */
    private Map<String, String> parseData(Map<Integer, String> headMap, Map<Integer, String> row, String tableName) {
        Map<String, String> map = new HashMap<>();
        for (Map.Entry<Integer, String> cell : row.entrySet()) {
            if (cell.getValue() == null) {
                continue;
            }
            String fieldC = headMap.get(cell.getKey()).trim();
            String fieldE = hmsChiEngDao.getFieldC2E(tableName, fieldC).trim();
            map.put(fieldE, cell.getValue());
        }
        return map;
    }

    /**
     * 存储数据库
     */
    private void saveOrUpdateData(Map<String, String> row, String tableNameC) {
        if (row.isEmpty()) {
            return;
        }
        String tableName = hmsChiEngDao.getTableNameC2E(tableNameC);
        if (ObjectUtils.isEmpty(tableName)) {
            throw new HMSException("没有该表名");
        }
        tableName = tableName.trim();
        String entityStr = entityRefPath + "." + getEntityStr(tableName);
        try {
            Class<?> modelClazz = Class.forName(entityStr);
            String id = getIdNameFromEntity(modelClazz);
            String idVal = row.get(id);
            if (StringUtils.isEmpty(idVal)) {
                throw new HMSException(tableNameC + "中" + id + "为主键，但导入表格中此数据项为空，请检查数据，已导入或更新数据全部回滚");
            }
            // 根据主键尝试作更新或者保存
            if (xlsxDao.existById(tableName, id, idVal)) {
                updateById(tableName, row, id, idVal);
            } else {
                save(tableName, row);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    private void updateById(String tableName, Map<String, String> row, String idName, String idVal) {
        xlsxDao.updateRowByMap(tableName, row, idName, idVal);
    }
    private void save(String tableName, Map<String, String> row) {
        xlsxDao.insertRowByMap(tableName, row);
    }

    /**
     * 通过model注解获取主键名(与数据库主键同名)
     * @param clazz 实体类型类
     * @return 主键名
     */
    private String getIdNameFromEntity(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            boolean fieldHasTableId = field.isAnnotationPresent(TableId.class);
            if (fieldHasTableId) {
                String idName = field.getName();
                return StrUtil.toUnderlineCase(idName);
            }
        }
        throw new HMSException("【源码级别错误】该实体类不存在主键注解，无法解析主键字段名，请联系开发人员。");
    }
    /**
     * 获取持久层简单路径类名
     * @param tableName 英文表名
     * @return
     */
    private String getDaoStr(String tableName) {
        String daoStr = null;
        if (tableName.length() >= 5 && "xlsx_".equals(tableName.substring(0, 5))) {
            tableName = tableName.substring(5);
        }
        daoStr = StrUtil.toCamelCase(tableName);
        daoStr = StrUtil.upperFirst(daoStr);
        daoStr += "Dao";

        return daoStr;
    }

    /**
     * 获取模型类简单路径类名
     * @param tableName 英文表名
     * @return
     */
    private String getEntityStr(String tableName) {
        String entityStr = null;
        if (tableName.length() >= 5 && "xlsx_".equals(tableName.substring(0, 5))) {
            tableName = tableName.substring(5);
        }
        entityStr = StrUtil.toCamelCase(tableName);
        entityStr = StrUtil.upperFirst(entityStr);
        entityStr += "Entity";
        return entityStr;
    }
}
